Skip to main content

Validator Modules

Overview

Validator modules (IValidator) handle transaction validation and signature verification in ERC-7579 smart accounts. They implement both UserOp validation for ERC-4337 and signature validation for ERC-1271.

Interface Definition

interface IValidator is IModule {
error InvalidTargetAddress(address target);

function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external returns (uint256);

function isValidSignatureWithSender(
address sender,
bytes32 hash,
bytes calldata data
) external view returns (bytes4);
}

Implementation Example: ECDSA Validator

contract ECDSAValidator is IValidator {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;

bytes4 private constant _ERC1271_MAGIC = 0x1626ba7e;
mapping(address => bool) private initializedAccounts;

// Core Module Functions
function onInstall(bytes calldata) external override {
if (isInitialized(msg.sender)) revert AlreadyInitialized(msg.sender);
initializedAccounts[msg.sender] = true;
}

function onUninstall(bytes calldata) external override {
if (!isInitialized(msg.sender)) revert NotInitialized(msg.sender);
initializedAccounts[msg.sender] = false;
}

function isModuleType(uint256 moduleTypeId) external pure override returns (bool) {
return moduleTypeId == MODULE_TYPE_VALIDATOR;
}

function isInitialized(address smartAccount) public view override returns (bool) {
return initializedAccounts[smartAccount];
}

// Validator-specific Functions
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external view override returns (uint256 validationData) {
bytes calldata signature = userOp.signature;

bytes32 r = bytes32(signature[0x00:0x20]);
bytes32 s = bytes32(signature[0x20:0x40]);
uint8 v = uint8(bytes1(signature[0x40:0x41]));

(address recoveredAddr, ECDSA.RecoverError error, ) = ECDSA.tryRecover(
userOpHash.toEthSignedMessageHash(),
v,
r,
s
);

return _isOwner(msg.sender, recoveredAddr)
? SIG_VALIDATION_SUCCESS
: SIG_VALIDATION_FAILED;
}

function isValidSignatureWithSender(
address,
bytes32 hash,
bytes calldata data
) external view returns (bytes4) {
address owner = msg.sender;
return SignatureCheckerLib.isValidSignatureNowCalldata(owner, hash, data)
? _ERC1271_MAGIC
: bytes4(0);
}

function _isOwner(
address smartAccount,
address addr
) private view returns (bool) {
bytes memory callData = abi.encodeWithSelector(
IOwnerManager.k1IsOwner.selector,
addr
);
// Assembly implementation for gas optimization
assembly {
let result := staticcall(
gas(),
smartAccount,
add(callData, 0x20),
mload(callData),
0x00,
0x20
)
if result {
isOwner := mload(0x00)
}
}
}
}

Key Features

  1. UserOp Validation

    • Validates ERC-4337 user operations
    • Handles signature verification
    • Integrates with account ownership system
  2. ERC-1271 Support

    • Contract signature validation
    • Standard signature interface
    • Multiple signature schemes
  3. Security Features

    • Signature replay protection
    • Owner validation
    • Gas optimization

Common Use Cases

  1. Multi-Signature Validation

    • Multiple owner validation
    • Threshold signatures
    • Time-locked operations
  2. Session Key Management

    • Time-limited permissions
    • Scoped permissions
    • Gas payment delegation
  3. Custom Authentication

    • Hardware wallet integration
    • Social recovery
    • MPC signatures

Security Considerations

  1. Signature Verification

    • Use standard libraries (OpenZeppelin, Solady)
    • Validate signature formats
    • Handle malformed signatures
  2. Access Control

    • Owner validation
    • Permission checks
    • Initialization status
  3. Gas Optimization

    • Efficient signature verification
    • Minimal storage usage
    • Assembly optimizations when appropriate

Implementation Guidelines

  1. Basic Structure
contract CustomValidator is IValidator {
// Track initialization status
mapping(address => bool) private initializedAccounts;

// Core module functions
function onInstall(bytes calldata data) external override {
// Installation logic
}

function onUninstall(bytes calldata data) external override {
// Cleanup logic
}

// Validator-specific functions
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external returns (uint256) {
// Validation logic
}

function isValidSignatureWithSender(
address sender,
bytes32 hash,
bytes calldata data
) external view returns (bytes4) {
// Signature validation logic
}
}
  1. Testing Requirements

    • UserOp validation tests
    • Signature verification tests
    • Access control tests
    • Gas optimization tests
  2. Integration Testing

    • Smart account interaction
    • ERC-4337 bundler compatibility
    • Gas estimation accuracy

Best Practices

  1. Error Handling

    • Use custom errors
    • Descriptive error messages
    • Proper validation checks
  2. Gas Optimization

    • Use assembly for repetitive operations
    • Minimize storage operations
    • Batch operations when possible
  3. Security

    • Implement replay protection
    • Validate all inputs
    • Use safe math operations